from Crypto.Cipher import AES
import base64
import os
import pandas as pd
import requests
import concurrent.futures
from urllib3.exceptions import InsecureRequestWarning
import logging
import time
from pathlib import Path

# 禁用SSL警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("report_download.log", encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 密钥和偏移量（与JS版本保持一致）
KEY = 'rzx1218appms1319'.encode('utf-8')
IV = 'rzx1218appms1319'.encode('utf-8')

# 下载基础URL
BASE_URL = "https://192.168.202.242:8088/app/appInformation/downloadFile?key="

# 下载目录
DOWNLOAD_DIR = r"F:\apk20250917\report"
# Excel文件路径
EXCEL_PATH = r"F:\apk20250917\reportUrls.xlsx"  # 请确保此路径正确
# 并发下载线程数
MAX_WORKERS = 5
# 是否模拟下载（不实际连接服务器，用于测试）
SIMULATION_MODE = False

def zero_pad(data, block_size=16):
    """实现与JS中CryptoJS.pad.ZeroPadding相同的填充方式"""
    padding_length = block_size - (len(data) % block_size)
    if padding_length == 0:
        padding_length = block_size
    return data + (b'\0' * padding_length)

def my_encrypt(word):
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    # 使用ZeroPadding（与JS版本一致）
    padded_data = zero_pad(word.encode('utf-8'))
    encrypted = cipher.encrypt(padded_data)
    return base64.b64encode(encrypted).decode('utf-8')

def my_decrypt(encrypted_word):
    try:
        cipher = AES.new(KEY, AES.MODE_CBC, IV)
        encrypted_data = base64.b64decode(encrypted_word)
        decrypted = cipher.decrypt(encrypted_data)
        # 移除ZeroPadding
        result = decrypted.rstrip(b'\0')
        return result.decode('utf-8')
    except Exception as e:
        logger.error(f"解密错误: {e}")
        return None

def download_file(url, index):
    try:
        # 加密URL
        encrypted = my_encrypt(url)
        # 再次base64编码
        double_encoded = base64.b64encode(encrypted.encode('utf-8')).decode('utf-8')
        # 构建完整下载URL
        download_url = f"{BASE_URL}{double_encoded}"
        
        # 从URL中提取文件名
        filename = url.split('/')[-1]
        file_path = os.path.join(DOWNLOAD_DIR, filename)
        
        # 检查文件是否已存在
        if os.path.exists(file_path):
            logger.info(f"[{index}] 文件已存在，跳过下载: {filename}")
            return True
        
        # 发送请求下载文件
        logger.info(f"[{index}] 开始下载: {filename}")
        logger.info(f"[{index}] 加密后的URL: {download_url}")
        start_time = time.time()
        
        if SIMULATION_MODE:
            # 模拟下载，创建一个简单的文件
            logger.info(f"[{index}] 模拟模式: 创建模拟文件 {filename}")
            time.sleep(1)  # 模拟下载延迟
            
            # 确保下载目录存在
            os.makedirs(DOWNLOAD_DIR, exist_ok=True)
            
            # 创建一个简单的模拟文件
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(f"这是一个模拟的文件内容，对应URL: {url}\n")
                f.write(f"加密后的URL: {download_url}\n")
            
            elapsed = time.time() - start_time
            logger.info(f"[{index}] 模拟下载完成: {filename} ({elapsed:.2f}秒)")
            return True
        else:
            # 实际下载
            response = requests.get(download_url, verify=False, stream=True)
            
            # 检查响应状态
            if response.status_code != 200:
                logger.error(f"[{index}] 下载失败，状态码: {response.status_code}, URL: {url}")
                return False
            
            # 确保下载目录存在
            os.makedirs(DOWNLOAD_DIR, exist_ok=True)
            
            # 保存文件
            with open(file_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
            
            elapsed = time.time() - start_time
            file_size = os.path.getsize(file_path) / (1024 * 1024)  # MB
            logger.info(f"[{index}] 下载完成: {filename} ({file_size:.2f} MB, {elapsed:.2f}秒)")
            return True
    
    except Exception as e:
        logger.error(f"[{index}] 下载过程中出错: {url}, 错误: {str(e)}")
        return False

def main():
    try:
        # 确保下载目录存在
        Path(DOWNLOAD_DIR).mkdir(parents=True, exist_ok=True)
        
        # 读取Excel文件
        logger.info(f"读取Excel文件: {EXCEL_PATH}")
        df = pd.read_excel(EXCEL_PATH)
        
        if 'url' not in df.columns:
            logger.error("Excel文件中没有找到'url'列")
            return
        
        # 获取URL列表
        urls = df['url'].tolist()
        total_urls = len(urls)
        logger.info(f"从Excel中读取了 {total_urls} 个URL")
        
        if SIMULATION_MODE:
            logger.info("运行在模拟模式，不会实际连接服务器")
        
        # 使用线程池下载文件
        successful = 0
        with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
            # 提交所有下载任务
            future_to_index = {executor.submit(download_file, url, i+1): i for i, url in enumerate(urls)}
            
            # 处理完成的任务
            for future in concurrent.futures.as_completed(future_to_index):
                index = future_to_index[future] + 1
                try:
                    if future.result():
                        successful += 1
                except Exception as e:
                    logger.error(f"任务 {index} 执行失败: {str(e)}")
        
        logger.info(f"下载完成: 成功 {successful}/{total_urls}")
    
    except Exception as e:
        logger.error(f"执行过程中出错: {str(e)}")

if __name__ == "__main__":
    main()